﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml;
using SuppKids.Core.Exceptions;
using SuppKids.Core.Providers;
using SuppKids.Licensing.Common;

namespace SuppKids.Core.Security
{
    public class LicensesManager
	{
		private const string licensePathFormat = "{0}/{1}.xml";

		private readonly LicenseStorage licenseStorage;

		private readonly IApplicationSettingsProvider settings;

		private readonly XmlRsaManager xmlRsaManager;

		public LicensesManager(LicenseStorage licenseStorage, IApplicationSettingsProvider settings, XmlRsaManager xmlRsaManager)
		{
			this.licenseStorage = licenseStorage;
			this.settings = settings;
			this.xmlRsaManager = xmlRsaManager;

            this.CreateDirectoryAndCheckAccessRights();
		}

        public void Activate(string licenseString)
        {
            this.CreateDirectoryAndCheckAccessRights();
            var xmlLicense = this.ParseToValidXmlLicense(licenseString);
            this.SaveOrUpdateLicense(xmlLicense);
        }

        public LicenseDTO SelectActiveLicense()
        {
            var licenses = this.SelectNotExpiredLicenses().ToList();
            var activatedLicenses = licenses.Where(x => x.ExpirationDate > DateTime.Now).ToList();
            var activeLicense = activatedLicenses.Any()
                ? activatedLicenses.OrderByDescending(x => x.ExpirationDate).First()
                : licenses.OrderByDescending(x => x.ExpirationDate).FirstOrDefault();

            return activeLicense;
        }

        private void SaveOrUpdateLicense(XmlDocument xmlLicense)
        {
            var license = xmlLicense.ToLicenseDTO();
            this.SaveOrUpdateLicense(xmlLicense, license.LicenseId);
        }

        private string SaveOrUpdateLicense(XmlDocument license, Guid instalationId)
		{
			string resultLicensePath;

			try
			{
				string licensePath = this.BuildLicensePath(instalationId);
				this.licenseStorage.WriteAllText(licensePath, license.OuterXml);
				resultLicensePath = licensePath;
			}
			catch (ErrorCodeException)
			{
				throw;
			}
			catch (Exception ex)
			{
				throw new ErrorCodeException(ErrorCodes.ActivationToolCode, ErrorCodes.ActivationTool.CannotSaveLicense, ex);
			}

			return resultLicensePath;
		}

        private XmlDocument ParseToValidXmlLicense(string licenseContent)
		{
			var resultXmlLicense = new XmlDocument();

			try
			{
				resultXmlLicense.LoadXml(licenseContent);
			}
			catch (Exception ex)
			{
				throw new ErrorCodeException(ErrorCodes.ActivationToolCode, ErrorCodes.ActivationTool.LicenseHasInvalidXml, ex);
			}

			if (!this.xmlRsaManager.VerifyXmlSignature(resultXmlLicense))
			{
				throw new ErrorCodeException(ErrorCodes.ActivationToolCode, ErrorCodes.ActivationTool.XmlSignatureVerificationFailed);
			}

			if (!this.VerifyCmid(resultXmlLicense))
			{
				throw new ErrorCodeException(ErrorCodes.ActivationToolCode, ErrorCodes.ActivationTool.CmidVerificationFailed);
			}

			return resultXmlLicense;
		}

        private void CreateDirectoryAndCheckAccessRights()
		{
			if (!this.licenseStorage.CreateDirectoryAndCheckAccessRights(this.settings.LicensesFolder))
			{
				throw new ErrorCodeException(ErrorCodes.ActivationToolCode, ErrorCodes.ActivationTool.NoAccessToLicensesFolder);
			}
		}

        private IEnumerable<LicenseDTO> SelectNotExpiredLicenses()
		{
		    return this.SelectValidLicenses().Where(x => x.ExpirationDate >= DateTime.Now);
		}

        private IEnumerable<LicenseDTO> SelectValidLicenses()
        {
            var files = Directory.GetFiles(settings.LicensesFolder);
            foreach (var file in files)
            {
                LicenseDTO license = null;

                try
                {
                    var licenseContent = this.licenseStorage.ReadAllText(file);
                    license = this.ParseToValidXmlLicense(licenseContent).ToLicenseDTO();
                }
                // ReSharper disable EmptyGeneralCatchClause
                // ReSharper disable UnusedVariable
                catch (Exception ignore)
                // ReSharper restore UnusedVariable
                // ReSharper restore EmptyGeneralCatchClause
                {
                    // if license is invalid then just do not include it to the valid license list
                }

                if (license != null)
                {
                    yield return license;
                }
            }
		}

		private bool VerifyCmid(XmlDocument xmlLicense)
		{
		    var license = xmlLicense.ToLicenseDTO();
			return license.CMID != null && license.CMID == ClientMachineManager.CMID;
		}

		private string BuildLicensePath(Guid installationId)
		{
			return string.Format(licensePathFormat, settings.LicensesFolder, installationId);
		}
	}
}
